(function (key) {
    fileset = function (/* ...parts */) {
        var file = Object.create(FileSet.prototype);
        var temp = FileSet.apply(file, Array.prototype.slice.call(arguments));
        return (temp != undefined ? temp : file);
    };

    FileSet = function (/* ...parts */) {
        var state = {};

        this.private = function (accessor) {
            return accessor === key && state;
        };

        state.dir = file.apply(null, Array.prototype.slice.call(arguments));
    };

    FileSet.prototype.dir = function () {
        var state = this.private(key);
        return (state.parent != null ? state.parent.dir() : state.dir);
    };

    FileSet.prototype.files = function () {
        var state = this.private(key);
        if (state.parent != null) {
            return state.parent.files().filter(state.filter);
        }
        else if (state.files == null) {
            state.files = this.dir().list();
        }
        return state.files;
    };

    FileSet.prototype.filter = function (filter) {
        var result = new FileSet(this.dir());
        var state = result.private(key);
        state.dir = null;
        state.parent = this;
        state.filter = Files.filter(Filter.parse(filter));
        return result;
    };

    FileSet.prototype.copyInto = function (dest) {
        if (dest.isFile()) throw new Error(dest + " is a file");
        var from = this.dir().path();
        var into = dest.path();
        var files = this.files().map(function (f) {
            return f.path();
        });
        console.log("copying", files.length, "file(s) from", from, "=>", into);
        Files_copyFiles(from, into, files);
        return dest;
    };

    FileSet.prototype.toString = function () {
        return "fileset(" + this.dir() + "[" + this.files() + "])";
    }
})({});
